use clippy_config::msrvs::{Msrv, OPTION_RESULT_IS_VARIANT_AND};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::ty::is_type_diagnostic_item;
use rustc_errors::Applicability;
use rustc_lint::LateContext;
use rustc_span::{sym, Span};

use super::MANUAL_IS_VARIANT_AND;

pub(super) fn check<'tcx>(
    cx: &LateContext<'_>,
    expr: &'tcx rustc_hir::Expr<'_>,
    map_recv: &'tcx rustc_hir::Expr<'_>,
    map_arg: &'tcx rustc_hir::Expr<'_>,
    map_span: Span,
    msrv: &Msrv,
) {
    // Don't lint if:

    // 1. the `expr` is generated by a macro
    if expr.span.from_expansion() {
        return;
    }

    // 2. the caller of `map()` is neither `Option` nor `Result`
    let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(map_recv), sym::Option);
    let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(map_recv), sym::Result);
    if !is_option && !is_result {
        return;
    }

    // 3. the caller of `unwrap_or_default` is neither `Option<bool>` nor `Result<bool, _>`
    if !cx.typeck_results().expr_ty(expr).is_bool() {
        return;
    }

    // 4. msrv doesn't meet `OPTION_RESULT_IS_VARIANT_AND`
    if !msrv.meets(OPTION_RESULT_IS_VARIANT_AND) {
        return;
    }

    let lint_msg = if is_option {
        "called `map(<f>).unwrap_or_default()` on an `Option` value"
    } else {
        "called `map(<f>).unwrap_or_default()` on a `Result` value"
    };
    let suggestion = if is_option { "is_some_and" } else { "is_ok_and" };

    span_lint_and_sugg(
        cx,
        MANUAL_IS_VARIANT_AND,
        expr.span.with_lo(map_span.lo()),
        lint_msg,
        "use",
        format!("{}({})", suggestion, snippet(cx, map_arg.span, "..")),
        Applicability::MachineApplicable,
    );
}
